define(['require', 'angular', 'text!root/file/getServer'], function (require, angular, fileServer) {
	var module = angular.module('ylc.common', ['ng'])
	ngHtmlDirective.$inject = ['$parse', '$compile'];
	function ngHtmlDirective($parse, $compile) {
		return {
			restrict: 'A',
			priority: 999,
			link: function (scope, element, attr) {
				var old;
				function getValue() {
					try {
						return scope.$eval(attr.ngHtml)
					} catch (e) {
						return scope[attr.ngHtml];
					}
				}
				scope.$watch(getValue, function ngBindHtmlWatchAction(value) {
					if (old == value) {
						return;
					}
					old = value;
					element.html(value);
					$compile(element.contents())(scope);
				});
			}
		}
	}
	module.directive('ngHtml', ngHtmlDirective)

	function autofocus() {
		return {
			restrict: 'A',
			link: function (scope, element, attr) {
				setTimeout(function () {
					element.focus()
				}, 500)

			}
		}
	}
	module.directive('autofocus', autofocus)

	function cellEditor($compile) {
		return {
			restrict: 'A',
			link: function (scope, element, attr) {
				var icon = $('<i class="fa fa-pencil" style="position: absolute;cursor:pointer;" ></i>')
				element.mouseover(function () {
					var pos = element.offset()
					$(document.body).append(icon)
					icon.unbind()
					icon.css({
						position: "absolute",
						'left': pos.left + element.width() - 12,
						'top': pos.top + element.height() / 2 - 6,
						'z-index': 200
					})
					icon.mouseout(function (e) {
						if (e.toElement != element[0]) {
							icon.remove()
						}
					})
					icon.click(function () {
						var chd = element.children().hide()
						scope.$$editorValue = element.text()
						var input = $('<input ng-model="$$editorValue" style="border:0;">')
						input.css({
							position: "absolute",
							'left': pos.left,
							'top': pos.top,
							'z-index': 300
						})
						input.width(element.width())
						input.height(element.height())
						$compile(input)(scope)
						$(document.body).append(input)
						input.focus()
					})
				})
				element.mouseout(function (e) {
					if (e.toElement != icon[0]) {
						icon.remove()
					}
				})
			}
		}
	}
	module.directive('cellEditor', cellEditor)
	module.directive('validator', function () {
		return {
			controller: function () {
				var ctrl = this;
				this.validator = this
				var validatorMap = {}
				var elementMap = {}
				function validate(attr) {
					return function (modelValue, viewValue) {
						var value = modelValue || viewValue;
						var valid = true
						if (attr.required && !value) {
							valid = false
						}
						validatorMap[attr.ngModel] = valid
						updateStatus();
						return valid;
					}
				}
				function updateStatus() {
					var validate = true;
					angular.forEach(validatorMap, function (v, k) {
						if (!v) {
							validate = false;
							ctrl.onInValidated && ctrl.onInValidated({
								$key: k
							})
						}
					})
					ctrl.validated = validate
					validate && ctrl.onValidated && ctrl.onValidated()
				}
				this.focusOnInvalid = function () {
					if (this.validated) {
						return true;
					}
					var keys = Object.keys(validatorMap)
					for (var i in keys) {
						var k = keys[i]
						if (!validatorMap[k]) {
							elementMap[k].focus()
							return false;
						}
					}
					return false;
				}
				this.register = function (ngModel, attr, element) {
					ngModel.$validators.formValidator = validate(attr)
					elementMap[attr.ngModel] = element;
					ngModel.$validators.formValidator(ngModel.$modelValue, ngModel.$viewValue)
				}
			},
			restrict: 'A',
			bindToController: {
				validated: '=?',
				validator: '=?',
				validateName: '@',
				onValidated: '&',
				onInValidated: '&'
			}
		}
	})
	module.directive('ngModel', function () {
		return {
			restrict: 'A',
			require: ['ngModel', '?^validator'],
			link: function (s, e, attr, ctrls) {
				var ngModel = ctrls[0], validator = ctrls[1]
				if (attr.ngModel && !attr.name) {
					attr.$set('name', attr.ngModel)
				}
				if (validator) {
					validator.register(ngModel, attr, e)
				}
			}
		}
	})
	module.directive('ngModelDate', function () {
		return {
			restrict: 'A',
			require: 'ngModel',
			priority: 1,
			compile: function compile(tElement, tAttrs, transclude) {
				return {
					post: function (s, e, attr, ngModel) {
						ngModel.$formatters.push(function (value) {
							if (value && !(value instanceof Date)) {
								value = new Date(value)
							}
							return value;
						});
					}
				}
			}
		}
	})
	module.directive('ngModelArray', function () {
		return {
			restrict: 'A',
			require: 'ngModel',
			priority: 1,
			compile: function compile(tElement, tAttrs, transclude) {
				return {
					post: function (s, e, attr, ngModel) {
						function toArray(value) {
							if (angular.isString(value)) {
								value = value.split(',')
								ngModel.$setViewValue(value)
							}
							return value;
						}
						ngModel.$formatters.push(toArray);
						ngModel.$parsers.push(toArray);
					}
				}

			}
		}
	})
	CtrlUtil.$inject = ['$http', '$injector']
	function CtrlUtil($http, $injector) {
		var CtrlUtil = this;
		this.getQueryParams = function (ctrl) {
			var param = {}
			angular.forEach(ctrl, function (v, k) {
				k = k + ''
				var i = k.lastIndexOf('QueryParam')
				if ((i > 0 && i == (k.length - 10)) && v != '') {
					param[k.substring(0, i)] = angular.isFunction(v) ? v() : v
				}
			})
			param.pageNumber = ctrl.pageNumber
			param.pageSize = ctrl.pageSize
			return param;
		}
		this.resetQueryParams = function (ctrl) {
			angular.forEach(ctrl, function (v, k) {
				k = k + ''
				var i = k.lastIndexOf('QueryParam')
				if ((i > 0 && i == (k.length - 10))) {
					ctrl[k] = ''
				}
			})
		}
		this.loadDepartment = function (target) {
			return $http.post(':root/department/queryByStatusAndType', {
				status: 1,
				type: 1
			}).success(function (r) {
				target.departments = r
				return r;
			})
		}
		this.loadMajor = function (target, department) {
			return $http.post(':root/major/getMajors', {
				department: department
			}).success(function (r) {
				target.majors = r
				return r;
			})
		}

		this.checkAll = function (ctrl, check) {
			angular.forEach(ctrl.entities.content, function (v, k) {
				v.$checked = check
			})
		}
		this.getChecked = function (ctrl) {
			var r = []
			angular.forEach(ctrl.entities.content, function (v, k) {
				v.$checked && r.push(v)
			})
			return r;
		}
		this.build = function (ctrl, query, paramObj) {
			paramObj = paramObj || ctrl
			ctrl.pageNumber = 0
			ctrl.pageSize = 20
			ctrl.query = function () {
				ctrl.entities && (ctrl.entities.content = {})
				var p = CtrlUtil.getQueryParams(paramObj)
				var p1 = CtrlUtil.getQueryParams(ctrl)
				angular.extend(p, p1)
				$http.post(query, p).success(function (r) {
					var q, b = ctrl.onQuerySuccess && (q = ctrl.onQuerySuccess(r))
					if (q && q.then) {
						q.then(function () {
							ctrl.entities = r;
						})
					} else {
						ctrl.entities = r;
					}
				})
			};
			ctrl.reset = function () {
				CtrlUtil.resetQueryParams(ctrl)
			};
			ctrl.checkAll = function (b) {
				CtrlUtil.checkAll(ctrl, b)
			}
		}
		this.toImageUrl = function (tpId, course) {
			var imageUrl = tpId ? (fileServer + '/preview/getImage?hash=' + tpId.split('/')[0] + '&size='
				+ tpId.split('/')[1] + (course ? '&w=280&h=170' : '')) : 'asset/imgs/logo_pure.png'
			return imageUrl;

		}
		this.toDownloadUrl = function (file, name) {
			var split = file.split('/')
			return require.toUrl('root/file/downloadPopByHash') + '?hash=' + split[0] + '&size=' + split[1] + '&name=' + name;
		}
		this.getIds = function (content) {
			return getPropertyArray(content, 'id');
		}
		this.getPropertyArray = function (content, property) {
			var props = []
			var resolved = property.split('.')
			angular.forEach(content, function (v) {
				var value = v;
				for (var i = 0; i < resolved.length; i++) {
					value = value[resolved[i]]
				}
				value && props.push(value)
			})
			return props;
		}
		this.addProperty = function (content, ext, field, idproperty, idtarget) {
			var map = {}
			angular.forEach(ext, function (v) {
				if (angular.isArray(v)) {
					map[v[0]] = v[1]
				} else if (angular.isObject(v)) {
					var id = v;
					if (idproperty) {
						var ar = idproperty.split('.')
						var t;
						while (t = ar.shift()) {
							id = id[t]
						}
					} else {
						id = id.id
					}
					map[id] = v
				}
			})
			angular.forEach(content, function (v) {
				var id = v;
				if (idtarget) {
					var ar = idtarget.split('.')
					var t;
					while (t = ar.shift()) {
						id = id[t]
					}
				} else {
					id = id.id
				}
				v[field] = map[id]
			})
		}
		return this;
	}
	module.factory('CtrlUtil', CtrlUtil)
	module.filter('toBool', function () {
		return function (arg) {
			var r = !!arg
			return r ? '是' : '否'
		}
	})
	module.filter('timeLeft', function () {
		return function (arg, format) {
			if (angular.isNumber(arg) && arg > 0) {
				var day = Math.floor(arg / (86400000))
				var description = ''
				if (day > 0) {
					description = description + day + '天'
				}
				var hours = Math.floor((arg - (day * 86400000)) / 3600000)
				if (hours > 0) {
					description = description + hours + '小时'
				}
				if (day <= 0 && hours <= 0) {
					var minuts = Math.floor((arg - (day * 86400000) - hours * 3600000) / 60000)
					if (minuts > 0) {
						description = description + minuts + '分钟'
					}
				}
				arg = description
			}
			return arg;
		}
	})
	module.filter('br', function () {
		return function (arg) {
			return arg ? arg.replace(/\r\n/g, '<br>').replace(/\n/g, '<br>').replace(/\r/g, '<br>') : arg;
		}
	})
	module.filter('toDate', function () {
		var cache = {}
		return function (arg) {
			var r = cache[arg]
			if (!r && angular.isDefined(arg)) {
				r = angular.isDate(arg) ? arg : new Date(arg);
				cache[arg] = r
			}
			return r;
		}
	})
	module.filter('unexpired', function ($rootScope) {
		return function (arg) {
			return $rootScope.$server && (arg > $rootScope.$server.time);
		}
	})
	module.filter('nullText', function () {
		return function (arg) {
			if (arg == undefined) {
				return '-'
			}
			return arg;
		}
	})
	module.run(function ($rootScope) {
		$rootScope.callNative = function () {
			var path = arguments[0].split('.')
			var target = window
			var self = target;
			for (var i = path.shift(); angular.isDefined(i); i = path.shift()) {
				self = target
				target = target[i]
			}
			var args = arguments[1]
			if (arguments[2]) {
				args = []
				for (var i = 0; i < arguments[1].length; i++) {
					var arg = arguments[1][i]
					angular.isDefined(arg) && args.push(arg)
				}
			}
			return target.apply(self, args)
		}
	})
	module.directive('input', function () {
		return {
			restrict: 'E',
			priority: 10,
			require: '?ngModel',
			link: function (scope, $el, attrs, ngModel) {
				if (attrs.type === 'number' && ngModel) {
					ngModel.$parsers.push(function (value) {
						return value;
					});

					ngModel.$formatters.push(function (value) {
						return parseFloat(value, 10);
					});
				}
			}
		}
	})
	module.factory('debounce', ['$timeout', '$q', function ($timeout, $q) {
		// The service is actually this function, which we call with the func
		// that should be debounced and how long to wait in between calls
		return function debounce(func, wait, immediate) {
			var timeout;
			// Create a deferred object that will be resolved when we need to
			// actually call the func
			var deferred = $q.defer();
			return function () {
				var context = this, args = arguments;
				var later = function () {
					timeout = null;
					if (!immediate) {
						deferred.resolve(func.apply(context, args));
						deferred = $q.defer();
					}
				};
				var callNow = immediate && !timeout;
				if (timeout) {
					$timeout.cancel(timeout);
				}
				timeout = $timeout(later, wait);
				if (callNow) {
					deferred.resolve(func.apply(context, args));
					deferred = $q.defer();
				}
				return deferred.promise;
			};
		};
	}]);
	function DataArray(arg) {
		var self = this;
		if (angular.isArray(arg)) {
			angular.forEach(arg, function (v) {
				self.push(v)
			})
		}
		this._add = []
		this._remove = []
		return this;
	}

	DataArray.prototype = new Array
	DataArray.prototype.push = function () {
		Array.prototype.push.apply(this, arguments)
		if (this.record) {
			Array.prototype.push.apply(this._add, arguments)
		}
	}
	DataArray.prototype.unshift = function () {
		Array.prototype.unshift.apply(this, arguments)
		if (this.record) {
			Array.prototype.push.apply(this._add, arguments)
		}
	}
	DataArray.prototype.splice = function () {
		var ret = Array.prototype.splice.apply(this, arguments)
		if (this.record) {
			Array.prototype.push.apply(this._remove, ret)
		}
	}
	DataArray.prototype.indexOf = function () {
		for (var i = 0; i < this.length; i++) {
			if (this[i] == arguments[0]) {
				return i;
			}
		}
		return -1;
	}
	DataArray.prototype.isDirty = function () {
		var dirty = this._add.length > 0 || this._remove.length > 0
		if (dirty) {
			return true;
		}
		for (var i = 0; i < this.length; i++) {
			if (this[i].$$update) {
				return true;
			}
		}
		return false;
	}
	DataArray.prototype.getUpdates = function () {
		var updates = []
		for (var i = 0; i < this.length; i++) {
			if (this[i].$$update) {
				updates.push(this[i])
			}
		}
		return updates;
	}
	DataArray.prototype.getAdds = function () {
		return this._add;
	}
	DataArray.prototype.clearAdds = function () {
		this._add = [];
	}
	DataArray.prototype.getRemoves = function () {
		return this._remove;
	}
	DataArray.prototype.clearRemoves = function () {
		this._remove = [];
	}
	DataArray.prototype.remove = function () {
		var removed = []
		for (var i in arguments) {
			var toRemove = arguments[i]
			var index = this.indexOf(toRemove)
			if (index > -1) {
				removed.push(toRemove)
				Array.prototype.splice.apply(this, [index, 1])
			}
		}
		if (this.record && removed.length > 0) {
			Array.prototype.push.apply(this._remove, removed)
		}
	}
	DataArray.prototype.shift = function () {
		var ret = Array.prototype.shift.apply(this, arguments)
		if (this.record) {
			Array.prototype.push.apply(this._remove, [ret])
		}
		return ret;
	}
	DataArray.prototype.pop = function () {
		var ret = Array.prototype.pop.apply(this, arguments)
		if (this.record) {
			Array.prototype.push.apply(this._remove, [ret])
		}
		return ret;
	}
	DataArray.prototype.startRecord = function () {
		this.record = true
	}
	DataArray.prototype.endRecord = function () {
		this.record = false
	}
	DataArray.prototype.checkAll = function (status) {
		for (var i = 0; i < this.length; i++) {
			this[i].$$checked = status
		}
	}
	DataArray.prototype.removeChecked = function () {
		DataArray.prototype.remove.apply(this, this.getChecked())
	}
	DataArray.prototype.getChecked = function () {
		var checked = []
		for (var i = 0; i < this.length; i++) {
			if (this[i].$$checked) {
				checked.push(this[i])
			}
		}
		return checked;
	}

	function DataCtrl($http, CtrlUtil, $injector, $scope, $q, $parse, debounce, $timeout, $element) {
		var dataCtrl = this
		this._page = {
			pageSize: 15,
			pageNumber: 0,
			total: 1,
			totalPage: 1
		}
		this._loadings = []
		this._data = new DataArray()
		this._data.record = true
		this._filters = {}
		this._joinFilters = {}
		this._orders = []
		this.customFields = []
		this.lastQueryParam = {}
		this.buildQueryForm = function (param) {
			var r = {}
			angular.extend(r, this._param)
			angular.extend(r, this._page)
			param && angular.extend(r, param)
			r.conditions = []
			r.orders = this._orders
			if (this.customFields && this.customFields.length > 0) {
				r.customFields = this.customFields
			}
			angular.forEach(this._filters, function (v) {
				r.conditions.push(v)
			})
			r.joinQuerys = []
			angular.forEach(this._joinFilters, function (v) {
				r.joinQuerys.push(v)
			})
			return r;
		}
		this.queryData = function (param) {
			this.lastQueryParam = param
			return $http.post(this._queryUrl, param).then(function (res) {
				return res.data;
			})
		}
		this._onDataLoad = []
		this.setData = function (data) {
			var promises = []
			angular.forEach(this._onDataLoad, function (func) {
				promises.push($q.resolve(func(data) || 1))
			})
			return $q.all(promises).then(function () {
				return $timeout(function () {//这里是否需要timeout？待测试
					dataCtrl._data.endRecord()
					dataCtrl._data = new DataArray(data)
					dataCtrl.current = data[0]
					dataCtrl._data.startRecord()
					return $timeout(function () {
						dataCtrl.afterRefresh({ data: data })
					})
				})
			})
		}
		this.getData = function () {
			return this._data;
		}
		this.getPage = function () {
			return this._page;
		}
		this.queryPage = function (param) {
			return this.queryData(this.buildQueryForm()).then(function (data) {
				dataCtrl._page.total = data.totalElements;
				dataCtrl._page.totalPage = data.totalPages;
				return dataCtrl.setData(data.content)
			}).then(function () {
				return dataCtrl.getData();
			})
		}
		this.queryOne = function () {
			return this.queryData(this.buildQueryForm({ page: false, single: true })).then(function (data) {
				return dataCtrl.setData([data])
			}).then(function () {
				return dataCtrl.getData();
			})
		}
		this.queryList = function () {
			return this.queryData(this.buildQueryForm({ page: false })).then(function (data) {
				return dataCtrl.setData(data)
			}).then(function () {
				return dataCtrl.getData();
			})
		}
		this.nextPage = function () {
			if (this._page.pageNumber < (this._page.totalPage - 1)) {
				this._page.pageNumber++
				this.debounceLoad()
			}
		}
		this.prevPage = function () {
			if (this._page.pageNumber > 0) {
				this._page.pageNumber--
				this.debounceLoad()
			}
		}
		this.debounceLoad = debounce(this.queryPage)
		this.setFilter = function (field, value, operator, logical, name) {
			this._filters[name || field] = {
				field: field,
				value: value,
				logical: logical || 'and',
				operator: operator || '='
			}
			if (this.autoLoad && this.$linked) {
				this.debounceLoad()
			}
			this.setMasterFilter()
		}
		this.setFilterObject = function (name, filter) {
			this._filters[name] = filter
			if (this.autoLoad && this.$linked) {
				this.debounceLoad()
			}
			this.setMasterFilter()
		}
		this.removeFilter = function (name) {
			delete this._filters[name]
			if (this.autoLoad && this.$linked) {
				this.debounceLoad()
			}
			this.setMasterFilter()
		}
		this.setMasterFilter = function () {
			if (this.masterCtrl && this.filterMaster) {
				var conditions = this.buildQueryForm().conditions
				var joinFilter = {
					slaveProperty: this.slaveProperty ? this.slaveProperty : this.idProperty,
					masterProperty: this.masterProperty,
					entity: this.entity,
					conditions: conditions
				}
				if (conditions.length == 0) {
					delete this.masterCtrl._joinFilters[this.slaveAs];
				} else {
					this.masterCtrl._joinFilters[this.slaveAs] = joinFilter;
				}
				if (this.masterCtrl.autoLoad && this.masterCtrl.$linked) {
					this.masterCtrl.debounceLoad()
				}
			}
		}
		this.setOrder = function (field, type) {
			if (!type) {
				this.removeOrder(field)
				return;
			}
			var order = { field: field, type: type }
			for (var i in this._orders) {
				if (this._orders[i].field == field) {
					this._orders[i] = order
					return;
				}
			}
			this._orders.push(order)
		}
		this.removeOrder = function (field) {
			for (var i in this._orders) {
				if (this._orders[i].field == field) {
					this._orders.splice(i, 1)
					return;
				}
			}
		}
		this.toJson = function (obj, pretty) {
			return JSON.stringify(obj, function (key, value) {
				var val = value;
				if (typeof key === "string" && key.charAt(0) === "$" && key.charAt(1) === "$") {
					val = undefined;
				} else if (value == window) {
					val = "$WINDOW";
				} else if (value && window.document === value) {
					val = "$DOCUMENT";
				} else if (value && value.$evalAsync && value.$watch) {
					val = "$SCOPE";
				} else if (angular.isDate(value)) {
					val = value.getTime()
				}
				return val;
			}, pretty);
		}
		var toast = debounce(function (str) {
			if (dataCtrl.showToast) {
				var modalService = $injector.get('modalService')
				modalService.error(str, 1500)
			}
		}, 500)
		this.flushChange = function () {
			return $q.all(this.doUpdate() || 1,
				this.doSave() || 1,
				this.doRemove() || 1)
		}
		this.doSave = debounce(function () {
			var data = this.getData();
			if (data.isDirty()) {
				var adds = data.getAdds();
				if (adds.length > 0) {
					return $http.post(this._saveUrl, { entity: this.entity, objects: this.toJson(adds) }).then(function (res) {
						data.clearAdds();
						for (var i = 0; i < adds.length; i++) {
							angular.extend(adds[i], res.data[i])
						}
						angular.forEach(dataCtrl._onDataLoad, function (func) {
							func(adds)
						})
						toast('保存成功')
						dataCtrl._page.total += adds.length
						return res;
					})
				}
			}
			return 0;
		})
		this.doRemove = debounce(function () {
			var data = this.getData();
			if (data.isDirty()) {
				var removes = data.getRemoves();
				var id = []
				angular.forEach(removes, function (v) {
					id.push(v[dataCtrl.idProperty])
				})
				if (id.length > 0) {
					return $http.post(this._removeUrl, { entity: this.entity, id: id }).then(function (res) {
						data.clearRemoves();
						toast('删除成功')
						dataCtrl._page.total -= removes.length
						return res;
					})

				}
			}
			return 0;
		})

		this.doUpdate = debounce(function () {
			var data = this.getData();
			if (data.isDirty()) {
				var updates = data.getUpdates();
				if (updates.length > 0) {
					return $http.post(this._updateUrl, { entity: this.entity, objects: this.toJson(updates) }).then(function (res) {
						angular.forEach(updates, function (v) {
							v.$$update = 0
						})
						toast('更新成功')
						return res;
					})
				}
			}
			return 0;
		})
		function loadSlaveData(data) {
			//many to one ,many to many?
			var isMany = !!dataCtrl.slaveProperty
			var field = isMany ? dataCtrl.slaveProperty : dataCtrl.idProperty
			var filter = {
				field: field,
				logical: 'and',
				operator: 'in'
			}
			var masterMap = {}
			var getMasterProp = $parse(dataCtrl.masterProperty)
			angular.forEach(data, function (v) {
				var key = getMasterProp(v)
				var arr = (masterMap[key] = masterMap[key] || [])
				arr.push(v)
			})
			filter.value = Object.keys(masterMap)
			dataCtrl._filters[field] = filter
			var fieldGetter = $parse(field)
			var params = dataCtrl.buildQueryForm({ page: false, single: false });
			delete dataCtrl._filters[field]
			return dataCtrl.queryData(params).then(function (sdata) {
				dataCtrl.setData(sdata)
				var slaveMap = {}
				angular.forEach(sdata, function (v) {
					var key = fieldGetter(v)
					var arr = (slaveMap[key] = slaveMap[key] || [])
					arr.push(v)
				})
				angular.forEach(masterMap, function (arr, k) {
					angular.forEach(arr, function (v) {
						v[dataCtrl.slaveAs] = isMany ? slaveMap[k] : (slaveMap[k] ? slaveMap[k][0] : undefined);
					})
				})
				return data;
			})
		}
		this.$onInit = function () {
			this.bindCtrl = dataCtrl
			this.idProperty = this.idProperty || 'id'
			this._queryUrl = this._queryUrl || ':root/common/query';
			this._updateUrl = this._updateUrl || ':root/common/update';
			this._saveUrl = this._saveUrl || ':root/common/save';
			this._removeUrl = this._removeUrl || ':root/common/remove';
			angular.isDefined(this._pageNumber) && (this._page.pageNumber = this._pageNumber)
			angular.isDefined(this._pageSize) && (this._page.pageSize = this._pageSize)
			this._param = {
				page: true,
				single: false,
				idProperty: this.idProperty,
				entity: this.entity
			}
			if (this.page != undefined) {
				this._param.page = this.page
			}
			if (this.masterCtrl && !this.filterOnly) {
				this.masterCtrl._onDataLoad.push(loadSlaveData)
			}
			$scope.$watch('$ctrl.getData()', function (nVal, oVal) {
				if (nVal.length == oVal.length && dataCtrl.getData().record) {
					if (dataCtrl._loadings.length > 0) {
						return;
					}
					for (var i = 0; i < nVal.length; i++) {
						var eq = angular.equals(nVal[i], oVal[i]);
						if (!eq) {
							(nVal[i].$$update = 1)
						}
					}
				}
			}, true)
		}
		this.$postLink = function () {
			if (this.autoLoad) {
				if (this._param.page) {
					this.queryPage()
				} else {
					this.queryList()
				}

			}
			this.$linked = true
		}
	}
	//module.controller('DataCtrl', DataCtrl)
	// data component
	module.component('data', {
		bindings: {
			_queryUrl: '<query',
			_updateUrl: '<update',
			_removeUrl: '<remove',
			_saveUrl: '<save',
			master: '<',
			autoLoad: '<',
			idProperty: '@',
			masterProperty: '@',
			slaveProperty: '@',
			slaveAs: '@',
			entity: '@',
			bindCtrl: '=?bindCtrl',
			afterRefresh: '&',
			_pageNumber: '<pageNumber',
			_pageSize: '<pageSize',
			showToast: '<',
			page: '<',
			filterMaster: '<',
			filterOnly: '<'
		},
		require: {
			masterCtrl: '?^^data'
		},
		controller: DataCtrl
	})

	module.component('order', {
		bindings: {
			field: '@',
			type: '@',
		},
		require: {
			masterCtrl: '^data'
		},
		controller: function () {
			this.$onChanges = function () {
				this.masterCtrl.setOrder(this.field, this.type)
			}
		}
	})
	module.component('calculate', {
		bindings: {
			field: '@',
		},
		require: {
			masterCtrl: '^data'
		},
		controller: function () {
			this.$onChanges = function () {
				this.masterCtrl.customFields.push(this.field)
			}
		}
	})

	module.component('pager', {
		bindings: {
			bindCtrl: '<',
		},
		transclude: true,
		templateUrl: ':root/components/common/data_pager.html',
		controller: function () {
		}
	})
	module.directive('exportCtrl', function () {
		return {
			restrict: 'A',
			bindToController: {
				bindCtrl: '<',
				exportCtrl: '=?'
			},
			controller: function ($element, $timeout) {
				var ctrl = this
				this.exportCtrl = this
				this.doExport = function (fileName, type) {
					require(['jszip', 'datatables.net', 'datatables.net-buttons', 'lib/datatables.net-buttons/js/buttons.colVis', 'lib/datatables.net-buttons/js/buttons.flash',
						'lib/datatables.net-buttons/js/buttons.html5', 'lib/datatables.net-buttons/js/buttons.print'], function (jszip) {
							window.JSZip = jszip
							ctrl.bindCtrl.queryList().then(function () {
								$timeout(function () {
									ctrl.table = ctrl.table || $element.DataTable({
										dom: 'Bfrtip',
										buttons: [
											{ filename: fileName, extend: 'excel', },
											{ filename: fileName, extend: 'pdf', }
										]
									});
									var data = ctrl.table.buttons(type).trigger();
								})

							})
						})
				}
			}
		}
	})
	module.component('filters', {
		bindings: {
			name: '@',
			logical: '@',
			enable: '<'
		},
		require: {
			masterCtrl: '?^data',
		},
		controller: function (debounce) {
			this._filters = {}
			var ctrl = this
			function toParent() {
				var keys = Object.keys(ctrl._filters)
				var disable = angular.isDefined(ctrl.enable) && !ctrl.enable
				if (keys.length > 0 && !disable) {
					var conditions = []
					angular.forEach(ctrl._filters, function (v) {
						conditions.push(v)
					})
					ctrl.masterCtrl.setFilterObject(ctrl.name, { logical: ctrl.logical || 'and', subConditions: conditions })
				} else {
					ctrl.masterCtrl.removeFilter(ctrl.name)
				}
			}
			this.setToParent = debounce(toParent)
			this.setFilter = function (field, value, operator, logical, name) {
				this._filters[name || field] = {
					field: field,
					value: value,
					logical: logical || 'and',
					operator: operator || '='
				}
				this.linked && this.setToParent()
			}
			this.removeFilter = function (name) {
				delete this._filters[name]
				this.linked && this.setToParent()
			}
			this.$postLink = function () {
				this.linked = true;
				toParent()
			}
		}
	})
	module.directive('filter', function () {
		return {
			restrict: 'AE',
			controller: function () {
				this.$onChanges = function () {
					if (this.bindCtrl) {
						if (this.removeOnNull && !(this.value || this.template)) {
							this.bindCtrl.removeFilter(this.name || this.field)
						} else if (angular.isDefined(this.enable) && !this.enable) {
							this.bindCtrl.removeFilter(this.name || this.field)
						} else {

							this.bindCtrl.setFilter(this.field, angular.isDefined(this.template) ? this.template : this.value, this.operator, this.logical, this.name || this.field)
						}

					}
				}
			},
			link: function ($scope, $element, $attr, controllers) {
				if (!controllers.filterCtrl.bindCtrl) {
					controllers.filterCtrl.bindCtrl = controllers.parentCtrl || controllers.masterCtrl
					controllers.filterCtrl.$onChanges()
				}
			},
			require: {
				masterCtrl: '?^data',
				filterCtrl: 'filter',
				parentCtrl: '?^filters'
			},
			bindToController: {
				field: '@',
				bindCtrl: '<',
				value: '<',
				template: '@',
				operator: '@',
				logical: '@',
				name: '@',
				enable: '<',
				removeOnNull: '<'
			}
		}
	})
	module.directive('upload', function () {
		return {
			restrict: 'A',
			require: ['ngModel', 'upload'],
			priority: 100,
			controller: function ($element, fileServer, $scope) {
				var ctrl = this
				var ngModel, value, view, input;
				input = document.createElement('input');
				input.setAttribute('type', 'file');
				var $input = $(input)
				function uploadComplete(data, file) {
					if (ctrl.allInOne) {
						value = data.message + '/' + data.start + '/' + file.name
					} else {
						if (ctrl.fileName) {
							value = ngModel.$modelValue || {}
							value[ctrl.fileName] = file.name
						}
						if (ctrl.fileId) {
							value = ngModel.$modelValue || {}
							value[ctrl.fileId] = data.message + '/' + data.start
						}
					}
					view = file.name
					ngModel.$setViewValue(file.name)
					ngModel.$render()
				}
				input.onchange = function () {
					var file = this.files[0];
					if (!file) {
						return;
					}
					var formdata = new FormData();
					formdata.append("file", file);
					$input.attr('disabled', 'disabled')
					jQuery.ajax({
						url: fileServer + '/fd',
						data: formdata,
						cache: false,
						contentType: false,
						processData: false,
						type: 'POST',
						success: function (data) {
							uploadComplete(data, file)
						}
					}).always(function () {
						$input.removeAttr('disabled')
					})
				};
				this.setNgModel = function (ngM) {
					ngModel = ngM
					ngModel.$parsers.push(function () {
						return value;
					})
					ngModel.$formatters.push(function () {
						if (ctrl.allInOne) {
							view = ngModel.$modelValue ? ngModel.$modelValue.split('/', 3)[2] : null;
						} else if (ctrl.fileName) {
							view = ngModel.$modelValue ? ngModel.$modelValue[ctrl.fileName] : null;
						}
						return view;
					})
					value = ngModel.$modelValue
					if ($element.attr('readonly') || $element.attr('disabled')) {
						return;
					}
					this.init();
				}
				this.disable = function () {
					$element.unbind('click')
					$element.css('cursor', '')
				}
				this.init = function () {
					$element.css('cursor', 'pointer')
					$element.click(function () {
						input.click();
					})
				}
				this.$onChanges = function () {
					input.setAttribute('accept', this.accept);
				}
			},
			link: function (s, e, attr, ctrls) {
				var ngModel = ctrls[0]
				ctrls[1].setNgModel(ngModel)
				attr.$observe('readonly', function (r) {
					if (r) {
						ctrls[1].disable()
					}
				})
			},
			bindToController: {
				fileName: '@',
				fileId: '@',
				allInOne: '<',
				accept: '@',
			}
		}
	})

	module.directive('download', function () {
		return {
			restrict: 'A',
			require: ['ngModel', 'download'],
			priority: 100,
			controller: function ($element, fileServer, $scope, $document) {
				var ctrl = this
				var ngModel, value, view, hash, size;
				this.setNgModel = function (ngM) {
					ngModel = ngM
					ngModel.$parsers.push(function () {
						return value;
					})
					ngModel.$formatters.push(function () {
						if (ctrl.allInOne) {
							var arr = ngModel.$modelValue ? ngModel.$modelValue.split('/', 3) : [];
							view = arr[2];
							hash = arr[0]
							size = arr[1]
						} else {
							if (ctrl.fileName) {
								view = ngModel.$modelValue ? ngModel.$modelValue[ctrl.fileName] : null;
							}
							if (ctrl.fileId) {
								var arr = ngModel.$modelValue ? ngModel.$modelValue.split('/', 2) : [];
								hash = arr[0]
								size = arr[1]
							}
						}
						a.attr('href', require.toUrl('root/file/downloadPopByHash') + '?hash=' + hash + '&size=' + size + '&name=' + view)
						return view;
					})
					value = ngModel.$modelValue
				}
				var a = $('<a target="_blank">')
				$element.before(a)
				a.append($element)
				//$element.css('cursor', 'pointer')
			},
			link: function (s, e, attr, ctrls) {
				var ngModel = ctrls[0]
				ctrls[1].setNgModel(ngModel)
			},
			bindToController: {
				fileName: '@',
				fileId: '@',
				allInOne: '<',
			}
		}
	})





	module.directive('ngRaty', [function () {
		return {
			restrict: "A",
			scope: {
				ngRaty: '=',
				ngModel: '=',
				mouseOver: '&',
				mouseOut: '&'
			},
			link: function ($scope, $element, $attrs) {
				var element = $($element);
				function safeApply(fn) {
					var phase = $scope.$root.$$phase;
					if (phase == '$apply' || phase == '$digest') {
						if (fn && (typeof (fn) === 'function')) {
							fn();
						}
					} else {
						$scope.$apply(fn);
					}
				};

				var rating = $scope.ngModel;
				var raty = {
					score: parseFloat(rating, 10),
					starType: 'i',
					click: function (stars, evt) {
						evt.stopPropagation();
						if (!stars) stars = 0;
						safeApply(function () {
							$scope.ngModel = parseFloat(stars);
						});
					},
					mouseover: function (stars, evt) {
						if (!$scope.mouseOver) return;
						safeApply(function () {
							$scope.mouseOver({ stars: stars, e: evt });
						});
					},
					mouseout: function (stars, evt) {
						if (!$scope.mouseOut) return;
						safeApply(function () {
							$scope.mouseOut({ stars: stars, e: evt });
						});
					}
				};
				var options = angular.extend(raty, $scope.ngRaty || {});
				element.raty(options);

				// Set view to score if model changes
				$scope.$watch('ngModel', function (newValue, oldValue) {
					element.raty('score', $scope.ngModel);
				});

				function destroy() {
					element.raty('destroy');
				}
				element.bind('$destroy', destroy);
			}
		}
	}]);
	Date.prototype.toJSON = function () {
		return this.getTime()
	}
	Date.prototype.setDay = function (dayOfWeek) {
		this.setDate(this.getDate() - this.getDay() + dayOfWeek);
		return this;
	};
	Date.prototype.setToZero = function () {
		this.setHours(0)
		this.setMinutes(0)
		this.setSeconds(0)
		this.setMilliseconds(0)
		return this;
	};
	return module;
})
